 /*******************************************************************************
  * Copyright (c) 2000, 2007 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
  * http://www.eclipse.org/legal/epl-v10.html
  *
  * Contributors:
  * IBM Corporation - initial API and implementation
  *******************************************************************************/

 package org.eclipse.ui.internal.ide.dialogs;

 import java.net.URI ;
 import java.net.URISyntaxException ;
 import org.eclipse.core.filesystem.EFS;
 import org.eclipse.core.filesystem.IFileInfo;
 import org.eclipse.core.filesystem.IFileStore;
 import org.eclipse.core.filesystem.URIUtil;
 import org.eclipse.core.resources.IPathVariableManager;
 import org.eclipse.core.resources.IResource;
 import org.eclipse.core.resources.IWorkspace;
 import org.eclipse.core.resources.ResourcesPlugin;
 import org.eclipse.core.runtime.IPath;
 import org.eclipse.core.runtime.IStatus;
 import org.eclipse.core.runtime.Path;
 import org.eclipse.core.runtime.Status;
 import org.eclipse.jface.dialogs.Dialog;
 import org.eclipse.jface.dialogs.IDialogConstants;
 import org.eclipse.swt.SWT;
 import org.eclipse.swt.events.ModifyEvent;
 import org.eclipse.swt.events.ModifyListener;
 import org.eclipse.swt.events.SelectionAdapter;
 import org.eclipse.swt.events.SelectionEvent;
 import org.eclipse.swt.events.SelectionListener;
 import org.eclipse.swt.graphics.Font;
 import org.eclipse.swt.graphics.FontMetrics;
 import org.eclipse.swt.graphics.GC;
 import org.eclipse.swt.layout.GridData;
 import org.eclipse.swt.layout.GridLayout;
 import org.eclipse.swt.widgets.Button;
 import org.eclipse.swt.widgets.Composite;
 import org.eclipse.swt.widgets.Control;
 import org.eclipse.swt.widgets.DirectoryDialog;
 import org.eclipse.swt.widgets.Event;
 import org.eclipse.swt.widgets.FileDialog;
 import org.eclipse.swt.widgets.Label;
 import org.eclipse.swt.widgets.Listener;
 import org.eclipse.swt.widgets.Text;
 import org.eclipse.ui.ide.dialogs.PathVariableSelectionDialog;
 import org.eclipse.ui.internal.ide.IDEWorkbenchMessages;
 import org.eclipse.ui.internal.ide.IDEWorkbenchPlugin;
 import org.eclipse.ui.internal.ide.filesystem.FileSystemConfiguration;
 import org.eclipse.ui.internal.ide.filesystem.FileSystemSupportRegistry;

 /**
  * Widget group for specifying a linked file or folder target.
  *
  * @since 2.1
  */
 public class CreateLinkedResourceGroup {
     private Listener listener;

     private String linkTarget = ""; //$NON-NLS-1$

     private int type;

     private boolean createLink = false;

     // used to compute layout sizes
 private FontMetrics fontMetrics;

     // widgets
 private Composite groupComposite;

     private Text linkTargetField;

     private Button browseButton;

     private Button variablesButton;

     private Label resolvedPathLabelText;

     private Label resolvedPathLabelData;

     private final IStringValue updatableResourceName;

     /**
      * Helper interface intended for updating a string value based on the
      * currently selected link target.
      *
      * @since 3.2
      */
     public static interface IStringValue {
         /**
          * Sets the String value.
          *
          * @param string
          * a non-null String
          */
         void setValue(String string);

         /**
          * Gets the String value.
          *
          * @return the current value, or <code>null</code>
          */
         String getValue();
     }

     private String lastUpdatedValue;

     private FileSystemSelectionArea fileSystemSelectionArea;

     /**
      * Creates a link target group
      *
      * @param type
      * specifies the type of resource to link to.
      * <code>IResource.FILE</code> or <code>IResource.FOLDER</code>
      * @param listener
      * listener to notify when one of the widgets' value is changed.
      * @param updatableResourceName
      * an updatable string value that will be updated to reflect the
      * link target's last segment, or <code>null</code>. Updating
      * will only happen if the current value of that string is null
      * or the empty string, or if it has not been changed since the
      * last time it was updated.
      */
     public CreateLinkedResourceGroup(int type, Listener listener,
             IStringValue updatableResourceName) {
         this.type = type;
         this.listener = listener;
         this.updatableResourceName = updatableResourceName;
         if (updatableResourceName != null) {
             lastUpdatedValue = updatableResourceName.getValue();
         }
     }

     /**
      * Creates the widgets
      *
      * @param parent
      * parent composite of the widget group
      * @return the widget group
      */
     public Composite createContents(Composite parent) {
         Font font = parent.getFont();
         initializeDialogUnits(parent);
         // top level group
 groupComposite = new Composite(parent, SWT.NONE);
         GridLayout layout = new GridLayout();
         groupComposite.setLayout(layout);
         groupComposite.setLayoutData(new GridData(GridData.VERTICAL_ALIGN_FILL
                 | GridData.FILL_HORIZONTAL));
         groupComposite.setFont(font);

         final Button createLinkButton = new Button(groupComposite, SWT.CHECK);
         if (type == IResource.FILE) {
             createLinkButton
                     .setText(IDEWorkbenchMessages.CreateLinkedResourceGroup_linkFileButton);
         } else {
             createLinkButton
                     .setText(IDEWorkbenchMessages.CreateLinkedResourceGroup_linkFolderButton);
         }
         createLinkButton.setSelection(createLink);
         createLinkButton.setFont(font);
         SelectionListener selectionListener = new SelectionAdapter() {
             public void widgetSelected(SelectionEvent e) {
                 createLink = createLinkButton.getSelection();
                 browseButton.setEnabled(createLink);
                 variablesButton.setEnabled(createLink);
                 // Set the required field color if the field is enabled
 linkTargetField.setEnabled(createLink);
                 if (fileSystemSelectionArea != null)
                     fileSystemSelectionArea.setEnabled(createLink);

                 if (listener != null) {
                     listener.handleEvent(new Event());
                 }
             }
         };
         createLinkButton.addSelectionListener(selectionListener);

         createLinkLocationGroup(groupComposite, createLink);
         return groupComposite;
     }

     /**
      * Creates the link target location widgets.
      *
      * @param locationGroup
      * the parent composite
      * @param enabled
      * sets the initial enabled state of the widgets
      */
     private void createLinkLocationGroup(Composite locationGroup,
             boolean enabled) {
         Button button = new Button(locationGroup, SWT.CHECK);
         int indent = button.computeSize(SWT.DEFAULT, SWT.DEFAULT).x;

         button.dispose();

         // linkTargetGroup is necessary to decouple layout from
 // resolvedPathGroup layout
 Composite linkTargetGroup = new Composite(locationGroup, SWT.NONE);
         GridLayout layout = new GridLayout();
         layout.numColumns = 4;
         layout.marginHeight = 0;
         layout.marginWidth = 0;
         linkTargetGroup.setLayout(layout);
         GridData data = new GridData(GridData.FILL_HORIZONTAL);
         data.horizontalIndent = indent;
         linkTargetGroup.setLayoutData(data);

         // link target location entry field
 linkTargetField = new Text(linkTargetGroup, SWT.BORDER);
         data = new GridData(GridData.FILL_HORIZONTAL);
         data.widthHint = IDialogConstants.ENTRY_FIELD_WIDTH;
         data.horizontalSpan = 2;
         linkTargetField.setLayoutData(data);
         linkTargetField.setEnabled(enabled);
         linkTargetField.addModifyListener(new ModifyListener() {
             public void modifyText(ModifyEvent e) {
                 linkTarget = linkTargetField.getText();
                 resolveVariable();
                 if (updatableResourceName != null) {
                     String value = updatableResourceName.getValue();
                     if (value == null
                             || value.equals("") || value.equals(lastUpdatedValue)) { //$NON-NLS-1$
 IPath linkTargetPath = new Path(linkTarget);
                         String lastSegment = linkTargetPath.lastSegment();
                         if (lastSegment != null) {
                             lastUpdatedValue = lastSegment;
                             updatableResourceName.setValue(lastSegment);
                         }
                     }
                 }
                 if (listener != null) {
                     listener.handleEvent(new Event());
                 }
             }
         });

         // browse button
 browseButton = new Button(linkTargetGroup, SWT.PUSH);
         browseButton
                 .setText(IDEWorkbenchMessages.CreateLinkedResourceGroup_browseButton);
         browseButton.addSelectionListener(new SelectionAdapter() {
             public void widgetSelected(SelectionEvent event) {
                 handleLinkTargetBrowseButtonPressed();
             }
         });
         browseButton.setEnabled(enabled);
         setButtonLayoutData(browseButton);

         // variables button
 variablesButton = new Button(linkTargetGroup, SWT.PUSH);
         variablesButton
                 .setText(IDEWorkbenchMessages.CreateLinkedResourceGroup_variablesButton);
         variablesButton.addSelectionListener(new SelectionAdapter() {
             public void widgetSelected(SelectionEvent event) {
                 handleVariablesButtonPressed();
             }
         });
         variablesButton.setEnabled(enabled);
         setButtonLayoutData(variablesButton);

         createFileSystemSelection(linkTargetGroup, enabled);

         createResolvedPathGroup(locationGroup, indent);

         if (linkTarget != null) {
             linkTargetField.setText(linkTarget);
         }
     }

     /**
      * Create the file system selection area.
      *
      * @param composite
      * @param enabled
      * the initial enablement state.
      */
     private void createFileSystemSelection(Composite composite, boolean enabled) {

         // Always use the default if that is all there is.
 if (FileSystemSupportRegistry.getInstance().hasOneFileSystem()) {
             return;
         }

         fileSystemSelectionArea = new FileSystemSelectionArea();
         fileSystemSelectionArea.createContents(composite);
         fileSystemSelectionArea.setEnabled(enabled);
     }

     /**
      * Create the composite for the resolved path.
      *
      * @param locationGroup
      * @param indent
      */
     private void createResolvedPathGroup(Composite locationGroup, int indent) {
         GridLayout layout;
         GridData data;
         Composite resolvedPathGroup = new Composite(locationGroup, SWT.NONE);
         layout = new GridLayout();
         layout.numColumns = 2;
         layout.marginHeight = 0;
         layout.marginWidth = 0;
         resolvedPathGroup.setLayout(layout);
         data = new GridData(GridData.FILL_HORIZONTAL);
         data.horizontalIndent = indent;
         resolvedPathGroup.setLayoutData(data);

         resolvedPathLabelText = new Label(resolvedPathGroup, SWT.SINGLE);
         resolvedPathLabelText
                 .setText(IDEWorkbenchMessages.CreateLinkedResourceGroup_resolvedPathLabel);
         resolvedPathLabelText.setVisible(false);

         resolvedPathLabelData = new Label(resolvedPathGroup, SWT.SINGLE);
         data = new GridData(GridData.FILL_HORIZONTAL);
         resolvedPathLabelData.setLayoutData(data);
         resolvedPathLabelData.setVisible(false);
     }

     /**
      * Returns a new status object with the given severity and message.
      *
      * @return a new status object with the given severity and message.
      */
     private IStatus createStatus(int severity, String message) {
         return new Status(severity, IDEWorkbenchPlugin.getDefault().getBundle()
                 .getSymbolicName(), severity, message, null);
     }

     /**
      * Disposes the group's widgets.
      */
     public void dispose() {
         if (groupComposite != null && groupComposite.isDisposed() == false) {
             groupComposite.dispose();
         }
     }

     /**
      * Returns the link target location entered by the user.
      *
      * @return the link target location entered by the user. null if the user
      * chose not to create a link.
      */
     public URI getLinkTargetURI() {
         if (!createLink)
             return null;
         // resolve path variable if we have a relative path
 if (!linkTarget.startsWith("/")) { //$NON-NLS-1$
 IPathVariableManager pathVariableManager = ResourcesPlugin
                     .getWorkspace().getPathVariableManager();
             try {
                 
                 URI path = new URI (linkTarget.replace(java.io.File.separatorChar, '/'));
                 URI resolved = pathVariableManager.resolveURI(path);
                 if (path != resolved) {
                     // we know this is a path variable, but return unresolved
 // path so resource will be created with variable intact
 return path;
                 }
             } catch (URISyntaxException e) {
                 // link target is not a valid URI. Fall through to handle this
 // below
 }
         }

         FileSystemConfiguration configuration = getSelectedConfiguration();
         if (configuration == null) {
             return URIUtil.toURI(linkTarget);
         }
         // validate non-local file system location
 return configuration.getContributor().getURI(linkTarget);
     }

     /**
      * Opens a file or directory browser depending on the link type.
      */
     private void handleLinkTargetBrowseButtonPressed() {
         IFileStore store = null;
         String selection = null;
         FileSystemConfiguration config = getSelectedConfiguration();
         boolean isDefault = config == null
                 || (FileSystemSupportRegistry.getInstance()
                         .getDefaultConfiguration()).equals(config);

         if (linkTarget.length() > 0) {
             store = IDEResourceInfoUtils.getFileStore(linkTarget);
             if (!store.fetchInfo().exists()) {
                 store = null;
             }
         }
         if (type == IResource.FILE) {
             if (isDefault) {
                 FileDialog dialog = new FileDialog(linkTargetField.getShell());
                 if (store != null) {
                     if (store.fetchInfo().isDirectory()) {
                         dialog.setFilterPath(linkTarget);
                     } else {
                         dialog.setFileName(linkTarget);
                     }
                 }
                 selection = dialog.open();
             } else {
                 URI uri = config.getContributor().browseFileSystem(linkTarget,
                         linkTargetField.getShell());
                 if (uri != null)
                     selection = uri.toString();
             }
         } else {
             String filterPath = null;
             if (store != null) {
                 IFileStore path = store;
                 if (!store.fetchInfo().isDirectory()) {
                     path = store.getParent();
                 }
                 if (path != null) {
                     filterPath = store.toString();
                 }
             }

             if (isDefault) {
                 DirectoryDialog dialog = new DirectoryDialog(linkTargetField
                         .getShell());
                 dialog
                         .setMessage(IDEWorkbenchMessages.CreateLinkedResourceGroup_targetSelectionLabel);
                 if (filterPath != null)
                     dialog.setFilterPath(filterPath);
                 selection = dialog.open();
             } else {
                 String initialPath = IDEResourceInfoUtils.EMPTY_STRING;
                 if (filterPath != null)
                     initialPath = filterPath;
                 URI uri = config.getContributor().browseFileSystem(initialPath,
                         linkTargetField.getShell());
                 if (uri != null)
                     selection = uri.toString();
             }
         }
         if (selection != null) {
             linkTargetField.setText(selection);
         }
     }

     /**
      * Return the selected configuration or <code>null</code> if there is not
      * one selected.
      *
      * @return FileSystemConfiguration or <code>null</code>
      */
     private FileSystemConfiguration getSelectedConfiguration() {
         if (fileSystemSelectionArea == null)
             return null;
         return fileSystemSelectionArea.getSelectedConfiguration();
     }

     /**
      * Opens a path variable selection dialog
      */
     private void handleVariablesButtonPressed() {
         int variableTypes = IResource.FOLDER;

         // allow selecting file and folder variables when creating a
 // linked file
 if (type == IResource.FILE) {
             variableTypes |= IResource.FILE;
         }

         PathVariableSelectionDialog dialog = new PathVariableSelectionDialog(
                 linkTargetField.getShell(), variableTypes);
         if (dialog.open() == IDialogConstants.OK_ID) {
             String [] variableNames = (String []) dialog.getResult();
             if (variableNames != null && variableNames.length == 1) {
                 linkTargetField.setText(variableNames[0]);
             }
         }
     }

     /**
      * Initializes the computation of horizontal and vertical dialog units based
      * on the size of current font.
      * <p>
      * This method must be called before <code>setButtonLayoutData</code> is
      * called.
      * </p>
      *
      * @param control
      * a control from which to obtain the current font
      */
     protected void initializeDialogUnits(Control control) {
         // Compute and store a font metric
 GC gc = new GC(control);
         gc.setFont(control.getFont());
         fontMetrics = gc.getFontMetrics();
         gc.dispose();
     }

     /**
      * Tries to resolve the value entered in the link target field as a
      * variable, if the value is a relative path. Displays the resolved value if
      * the entered value is a variable.
      */
     private void resolveVariable() {
         IPathVariableManager pathVariableManager = ResourcesPlugin
                 .getWorkspace().getPathVariableManager();
         IPath path = new Path(linkTarget);
         IPath resolvedPath = pathVariableManager.resolvePath(path);

         if (path.equals(resolvedPath)) {
             resolvedPathLabelText.setVisible(false);
             resolvedPathLabelData.setVisible(false);
         } else {
             resolvedPathLabelText.setVisible(true);
             resolvedPathLabelData.setVisible(true);
         }
         resolvedPathLabelData.setText(resolvedPath.toOSString());
     }

     /**
      * Sets the <code>GridData</code> on the specified button to be one that
      * is spaced for the current dialog page units. The method
      * <code>initializeDialogUnits</code> must be called once before calling
      * this method for the first time.
      *
      * @param button
      * the button to set the <code>GridData</code>
      * @return the <code>GridData</code> set on the specified button
      */
     private GridData setButtonLayoutData(Button button) {
         GridData data = new GridData(GridData.HORIZONTAL_ALIGN_FILL);
         int widthHint = Dialog.convertHorizontalDLUsToPixels(fontMetrics,
                 IDialogConstants.BUTTON_WIDTH);
         data.widthHint = Math.max(widthHint, button.computeSize(SWT.DEFAULT,
                 SWT.DEFAULT, true).x);
         button.setLayoutData(data);
         return data;
     }

     /**
      * Sets the value of the link target field
      *
      * @param target
      * the value of the link target field
      */
     public void setLinkTarget(String target) {
         linkTarget = target;
         if (linkTargetField != null && linkTargetField.isDisposed() == false) {
             linkTargetField.setText(target);
         }
     }

     /**
      * Validates the type of the given file against the link type specified in
      * the constructor.
      *
      * @param linkTargetFile
      * file to validate
      * @return IStatus indicating the validation result. IStatus.OK if the given
      * file is valid.
      */
     private IStatus validateFileType(IFileInfo linkTargetFile) {
         if (type == IResource.FILE && linkTargetFile.isDirectory()) {
             return createStatus(
                     IStatus.ERROR,
                     IDEWorkbenchMessages.CreateLinkedResourceGroup_linkTargetNotFile);
         } else if (type == IResource.FOLDER && !linkTargetFile.isDirectory()) {
             return createStatus(
                     IStatus.ERROR,
                     IDEWorkbenchMessages.CreateLinkedResourceGroup_linkTargetNotFolder);
         }
         return Status.OK_STATUS;
     }

     /**
      * Validates this page's controls.
      *
      * @param linkHandle
      * The target to check
      *
      * @return IStatus indicating the validation result. IStatus.OK if the
      * specified link target is valid given the linkHandle.
      */
     public IStatus validateLinkLocation(IResource linkHandle) {
         if (linkTargetField == null || linkTargetField.isDisposed()
                 || !createLink) {
             return Status.OK_STATUS;
         }
         IWorkspace workspace = IDEWorkbenchPlugin.getPluginWorkspace();
         FileSystemConfiguration configuration = getSelectedConfiguration();
         if (configuration == null
                 || EFS.SCHEME_FILE.equals(configuration.getScheme())) {
             // Special handling for UNC paths. See bug 90825
 IPath location = new Path(linkTarget);
             if (location.isUNC()) {
                 return createStatus(
                         IStatus.WARNING,
                         IDEWorkbenchMessages.CreateLinkedResourceGroup_unableToValidateLinkTarget);
             }
         }
         URI locationURI = getLinkTargetURI();
         IStatus locationStatus = workspace.validateLinkLocationURI(linkHandle,
                 locationURI);
         if (locationStatus.getSeverity() == IStatus.ERROR) {
             return locationStatus;
         }

         // use the resolved link target name
 URI resolved = workspace.getPathVariableManager().resolveURI(
                 locationURI);
         IFileInfo linkTargetFile = IDEResourceInfoUtils.getFileInfo(resolved);
         if (linkTargetFile != null && linkTargetFile.exists()) {
             IStatus fileTypeStatus = validateFileType(linkTargetFile);
             if (!fileTypeStatus.isOK()) {
                 return fileTypeStatus;
             }
         } else if (locationStatus.isOK()) {
             // locationStatus takes precedence over missing location warning.
 return createStatus(
                     IStatus.WARNING,
                     IDEWorkbenchMessages.CreateLinkedResourceGroup_linkTargetNonExistent);
         }
         return locationStatus;
     }
 }

